#include "File.h"

#include <fstream>
#include <Windows.h>

// temp
#include <iostream>
using namespace std;

File::File()
  : mp_data_piece(nullptr), m_is_valid(false)
  {

  }

File::File(const wstring& i_fname)
  : m_file_name(i_fname), mp_data_piece(nullptr)
  {
  // temp storage for file name
  LPCTSTR file_name = m_file_name.c_str();
  
  // checking file
  DWORD atr = GetFileAttributes(file_name);
  if (atr == INVALID_FILE_ATTRIBUTES)
    m_is_valid = false;
  else
    m_is_valid = true;
  
  if (m_is_valid)
    {
    // opening file
    mh_file = CreateFile(
      file_name,
      GENERIC_READ,
      FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
      NULL,
      OPEN_EXISTING,
      FILE_ATTRIBUTE_NORMAL,
      NULL);

    if (mh_file == INVALID_HANDLE_VALUE)
      {
      m_is_valid = false;
      m_file_size = 0;
      return;
      }
        
    // getting file size
    DWORD f_size[2];
    f_size[0] = GetFileSize(mh_file, &f_size[1]);
    if (f_size[0] == INVALID_FILE_SIZE)
      {
      m_is_valid = false;
      return;
      }
    m_file_size = *(unsigned __int64*)(&f_size);

    m_is_valid = true;

    // copying piece of data from file to buffer
    mp_data_piece = new char[g_data_piece_size];
    LPDWORD bytes_read = new DWORD;
    BOOL read = ReadFile(
      mh_file,
      mp_data_piece,
      g_data_piece_size,
      bytes_read,
      NULL
      );

    if (read)
      {
      m_data_piece_size = static_cast<size_t>(*bytes_read);
      }
    else
      {
      m_is_valid = false;
      }
    delete bytes_read;
    }
  }

File::File(const File& i_other)
  : m_file_name(i_other.m_file_name), mp_data_piece(nullptr)
  {
  m_bytes_done = i_other.m_bytes_done;
  m_data_piece_size = i_other.m_data_piece_size;
  m_file_size = i_other.m_file_size;
  m_is_reading = i_other.m_is_reading;
  m_is_valid = i_other.m_is_valid;
  
  if (m_is_valid)
    {
    LPCTSTR file_name = m_file_name.c_str();
    mh_file = CreateFile(
      file_name,
      GENERIC_READ,
      FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
      NULL,
      OPEN_EXISTING,
      FILE_ATTRIBUTE_NORMAL,
      NULL);
    mp_data_piece = new char[m_data_piece_size];
    memcpy(mp_data_piece, i_other.mp_data_piece, m_data_piece_size);
    }
  else
    {
    mh_file = INVALID_HANDLE_VALUE;
    }
  }

File::File(const File&& i_ref)
  {
  m_file_name = move(i_ref.m_file_name);
  m_data_piece_size = move(i_ref.m_data_piece_size);
  m_bytes_done = move(i_ref.m_bytes_done);
  m_file_name = move(i_ref.m_file_name);
  m_file_size = move(i_ref.m_file_size);
  m_is_reading = move(i_ref.m_is_reading);
  m_is_valid = move(i_ref.m_is_valid);
  mp_data_piece = move(i_ref.mp_data_piece);
  mh_file = move(i_ref.mh_file);
  }

File::~File()
  {
  delete[] mp_data_piece;
  if (m_is_valid)
    {
    CloseHandle(mh_file);
    }
  }

bool File::IsValid() const
  {
  return m_is_valid;
  }

int File::Delete()
  {
  if (!m_is_valid)
    return -1;
  int res = DeleteFile(m_file_name.c_str());
  if (res != 0) 
    return res;
  m_is_valid = false;
  return 0;
  }

bool File::operator==(const File& i_other) const
  {
  if (!m_is_valid || !i_other.m_is_valid)
    return false;
  // comparing file sizes
  if (this->m_file_size != i_other.m_file_size)
    return false;
  
  // comparing buffers
  int n = memcmp(this->mp_data_piece, i_other.mp_data_piece, this->m_data_piece_size);
  if (n != 0)
    return false;

  // full compare
  if (this->m_data_piece_size == this->m_file_size)
    return true;
  return (this->_FullCompare(i_other) == 0 ? true : false);
  }

bool File::operator<(const File& i_other) const
  {
  // invalid file is LESS than valid
  if (!m_is_valid && i_other.m_is_valid)
    return true;
  if (m_is_valid && !i_other.m_is_valid)
    return false;
  // invalid files are EQUAL
  if (!m_is_valid && !i_other.m_is_valid)
    return false;
  
  // comparing size
  if (m_file_size < i_other.m_file_size)
    return true;
  else if (m_file_size > i_other.m_file_size)
    return false;
  else
    {
    // comparing data_pieces
    int cmp = memcmp(mp_data_piece, i_other.mp_data_piece, m_data_piece_size);
    if (cmp < 0)
      return true;
    else if (cmp > 0)
      return false;
    else
      {
      // full comparing
      int deep_cmp = this->_FullCompare(i_other);
      if (deep_cmp < 0)
        return true;
      else
        return false;
      }
    }
  }

int File::_FullCompare(const File& i_other) const
  {
  if (!this->m_is_valid || !i_other.m_is_valid)
    return false;

  int result = 0;

  char *p_data_this = nullptr;
  char *p_data_other = nullptr;
  
  // trying to allocate first buffer
  while (!p_data_this)
    {
    p_data_this = new(nothrow) char[g_data_piece_size];
    }

  // trying to allocate second buffer
  while (!p_data_other)
    {
    p_data_other = new(nothrow) char[g_data_piece_size];
    }

  // setting file pointers
  SetFilePointer(mh_file, m_data_piece_size, NULL, 0);
  SetFilePointer(i_other.mh_file, m_data_piece_size, NULL, 0);

  // comparing files
  m_bytes_done = 0;
  i_other.m_bytes_done = 0;
  m_is_reading = true;
  i_other.m_is_reading = true;
  BOOL read;
  size_t bytes_read;
  do
    {
    // reading piece of first file
    LPDWORD p_bytes_read = new DWORD;
    read = ReadFile(
      this->mh_file,
      p_data_this,
      g_data_piece_size,
      p_bytes_read,
      NULL
      );
    if (!read)
      {
      result = 1;
      break;
      }
      
    bytes_read = static_cast<size_t>(*p_bytes_read);
    m_bytes_done += bytes_read;

    // reading piece of second file
    read = ReadFile(
      i_other.mh_file,
      p_data_other,
      g_data_piece_size,
      p_bytes_read,
      NULL
      );
    if (!read || (bytes_read != *p_bytes_read))
      {
      result = 1;
      break;
      }

    int cmp_res = memcmp(p_data_this, p_data_other, bytes_read);
    if (cmp_res != 0)
      {
      result = cmp_res;
      break;
      }
    delete p_bytes_read;
    } while(bytes_read == g_data_piece_size);

  m_is_reading = false;
  i_other.m_is_reading = false;
  delete[] p_data_this;
  delete[] p_data_other;
  return result;
  }

wstring File::GetName() const
  {
  return m_file_name;
  }

unsigned long long File::GetSize() const
  {
  return m_file_size;
  }

size_t File::GetDataPieceSize() const
  {
  return m_data_piece_size;
  }

bool File::IsReading() const
  {
  return m_is_reading;
  }